<html>
<head>
<!-- Some sections based on a WebGL tutorial at http://learningwebgl.com/lessons/lesson05/index.html -->
<!--
Copyright (c) 2010 Mariano M. Chouza

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
-->
<title>WebGL Schr&ouml;dinger Simulator</title>
<script id="shader-vs" type="x-shader/x-vertex">
attribute vec3 aVertexPosition;
attribute vec2 aTextureCoord;

varying vec2 vTextureCoord;

void main(void)
{
    gl_Position = vec4(aVertexPosition, 1.0);
    vTextureCoord = aTextureCoord;
}
</script>
<script id="shader-fs-init" type="x-shader/x-fragment">
precision highp float;
precision highp int;

varying vec2 vTextureCoord;

void main(void)
{
    vec2 delta = vTextureCoord - vec2(0.5, 0.25);
    
    vec2 psi = exp(-dot(delta, delta) / 0.01) * vec2(sin(delta.y * 192.0), cos(delta.y * 192.0));
    
    psi = psi / 2.0 + 0.5;
    
    vec2 psiLo = mod(psi, 1.0 / 255.0);
    vec2 psiHi = psi - psiLo;
    psiLo *= 255.0;

    gl_FragColor = vec4(psiHi.x, psiLo.x, psiHi.y, psiLo.y);
}
</script>
<script id="shader-fs-init-v" type="x-shader/x-fragment">
precision highp float;
precision highp int;

varying vec2 vTextureCoord;

void main(void)
{
	vec2 delta = vTextureCoord - vec2(0.45, 0.5);
    vec2 delta2 = vTextureCoord - vec2(0.55, 0.5);
    
    /*
    float v = 0.5 * exp(-dot(delta, delta) * 10000.0);
    v += 0.5 * exp(-dot(delta2, delta2) * 10000.0);
    */
    float v = 0.0;
    if (delta.y > 0.0 && delta.y < 0.02) {
        v = 0.5;
    }
    if (abs(delta.x) < 0.01 || abs(delta2.x) < 0.01) {
        v = 0.0;
    }
    
	float vLo = mod(v, 1.0 / 255.0);
	float vHi = v - vLo;
	vLo *= 255.0;
    gl_FragColor = vec4(vHi, vLo, 0.0, 0.0);
}
</script>
<script id="shader-fs-get-k" type="x-shader/x-fragment">
// i hbar d/dt psi = -hbar^2/(2m) lap psi + v psi
// hbar = m = 1
// i d/dt psi = -1/2 lap psi + v psi
// d/dt psi = i/2 lap psi - i v psi
// d/dt (psi_r + i psi_i) = i/2 lap (psi_r + i psi_i) - i v (psi_r + i psi_i)
// d/dt (psi_r + i psi_i) = i/2 lap psi_r - 1/2 lap psi_i - i v psi_r + v psi_i
// d/dt psi_r = -1/2 lap psi_i + v psi_i
// d/dt psi_i = 1/2 lap psi_r - v psi_r

// the laplacian operator can be aproximated by the following kernel:
// [ 0 -1  0 ]
// [-1  4 -1 ]
// [ 0 -1  0 ]

precision highp float;
precision highp int;

varying vec2 vTextureCoord;

uniform highp sampler2D uSampler0, uSampler1;
uniform int uTexSize;

void main(void)
{
    float delta = 1.0 / float(uTexSize);

    vec4 psiColor = texture2D(uSampler0, vTextureCoord);
    vec4 psiWColor = texture2D(uSampler0, vec2(vTextureCoord.s - delta, vTextureCoord.t));
    vec4 psiEColor = texture2D(uSampler0, vec2(vTextureCoord.s + delta, vTextureCoord.t));
    vec4 psiSColor = texture2D(uSampler0, vec2(vTextureCoord.s, vTextureCoord.t - delta));
    vec4 psiNColor = texture2D(uSampler0, vec2(vTextureCoord.s, vTextureCoord.t + delta));
	vec4 vColor = texture2D(uSampler1, vTextureCoord);
    
    vec2 psi = 2.0 * (psiColor.rb + psiColor.ga / 255.0) - 1.0;
    vec2 psiW = 2.0 * (psiWColor.rb + psiWColor.ga / 255.0) - 1.0;
    vec2 psiE = 2.0 * (psiEColor.rb + psiEColor.ga / 255.0) - 1.0;
    vec2 psiS = 2.0 * (psiSColor.rb + psiSColor.ga / 255.0) - 1.0;
    vec2 psiN = 2.0 * (psiNColor.rb + psiNColor.ga / 255.0) - 1.0;
	float v = -(vColor.r + vColor.g / 255.0);
    
    vec2 lap = 4.0 * psi - psiW - psiE - psiS - psiN;
    
    vec2 psiDot = vec2(-0.5 * lap.y + v * psi.y, 0.5 * lap.x - v * psi.x) / 2.0 + 0.5;
    vec2 psiDotLo = mod(psiDot, 1.0 / 255.0);
    vec2 psiDotHi = psiDot - psiDotLo;
    psiDotLo *= 255.0;
    
    gl_FragColor = vec4(psiDotHi.x, psiDotLo.x, psiDotHi.y, psiDotLo.y);
}
</script>
<script id="shader-fs-get-psi-dot" type="x-shader/x-fragment">
precision highp float;
precision highp int;

varying vec2 vTextureCoord;

uniform highp sampler2D uSampler0, uSampler1, uSampler2, uSampler3;
uniform int uTexSize;

void main(void)
{
    vec4 k1Color = texture2D(uSampler0, vTextureCoord);
    vec4 k2Color = texture2D(uSampler1, vTextureCoord);
    vec4 k3Color = texture2D(uSampler2, vTextureCoord);
    vec4 k4Color = texture2D(uSampler3, vTextureCoord);
    
	vec2 k1 = 2.0 * (k1Color.rb + k1Color.ga / 255.0) - 1.0;
	vec2 k2 = 2.0 * (k2Color.rb + k2Color.ga / 255.0) - 1.0;
	vec2 k3 = 2.0 * (k3Color.rb + k3Color.ga / 255.0) - 1.0;
	vec2 k4 = 2.0 * (k4Color.rb + k4Color.ga / 255.0) - 1.0;
    
    vec2 psiDot = ((k1 + 2.0 * (k2 + k3) + k4) / 6.0) / 2.0 + 0.5;
    vec2 psiDotLo = mod(psiDot, 1.0 / 255.0);
    vec2 psiDotHi = psiDot - psiDotLo;
    psiDotLo *= 255.0;
    
    gl_FragColor = vec4(psiDotHi.x, psiDotLo.x, psiDotHi.y, psiDotLo.y);
}
</script>
<script id="shader-fs-half-euler-step" type="x-shader/x-fragment">
precision highp float;
precision highp int;

varying vec2 vTextureCoord;

uniform highp sampler2D uSampler0, uSampler1;
uniform int uTexSize;

void main(void)
{
    vec4 psiColor = texture2D(uSampler0, vTextureCoord);
    vec4 psiDotColor = texture2D(uSampler1, vTextureCoord);
	
	vec2 psi = 2.0 * (psiColor.rb + psiColor.ga / 255.0) - 1.0;
	vec2 psiDot = 2.0 * (psiDotColor.rb + psiDotColor.ga / 255.0) - 1.0;
	
	psi += psiDot * 0.1;
	
	psi = psi / 2.0 + 0.5;
	vec2 psiLo = mod(psi, 1.0 / 255.0);
	vec2 psiHi = psi - psiLo;
	psiLo *= 255.0;
	
	gl_FragColor = vec4(psiHi.x, psiLo.x, psiHi.y, psiLo.y);
}
</script>
<script id="shader-fs-euler-step" type="x-shader/x-fragment">
precision highp float;
precision highp int;

varying vec2 vTextureCoord;

uniform highp sampler2D uSampler0, uSampler1;
uniform int uTexSize;

void main(void)
{
    vec4 psiColor = texture2D(uSampler0, vTextureCoord);
    vec4 psiDotColor = texture2D(uSampler1, vTextureCoord);
	
	vec2 psi = 2.0 * (psiColor.rb + psiColor.ga / 255.0) - 1.0;
	vec2 psiDot = 2.0 * (psiDotColor.rb + psiDotColor.ga / 255.0) - 1.0;
	
	psi += psiDot * 0.2;
	
	psi = psi / 2.0 + 0.5;
	vec2 psiLo = mod(psi, 1.0 / 255.0);
	vec2 psiHi = psi - psiLo;
	psiLo *= 255.0;
	
	gl_FragColor = vec4(psiHi.x, psiLo.x, psiHi.y, psiLo.y);
}
</script>
<script id="shader-fs-get-bounds" type="x-shader/x-fragment">
precision highp float;
precision highp int;

varying vec2 vTextureCoord;

uniform highp sampler2D uSampler0, uSampler1;
uniform int uTexSize;

void main(void)
{
    vec4 psiColor = texture2D(uSampler0, vTextureCoord);
    
	vec2 psi = 2.0 * (psiColor.rb + psiColor.ga / 255.0) - 1.0;
    
	float psiMod = sqrt(dot(psi, psi));
	
	float psiModLo = mod(psiMod, 1.0 / 255.0);
    float psiModHi = psiMod - psiModLo;
    psiModLo *= 255.0;
	
	gl_FragColor = vec4(psiModHi, psiModLo, 0.0, 0.0);
}
</script>
<script id="shader-fs-red-bounds" type="x-shader/x-fragment">
precision highp float;
precision highp int;

varying vec2 vTextureCoord;

uniform highp sampler2D uSampler0;
uniform int uTexSize;

void main(void)
{
    float delta = 0.5 / float(uTexSize);

    vec4 boundsNWColor = texture2D(uSampler0, vec2(vTextureCoord.s - delta, vTextureCoord.t + delta));
    vec4 boundsNEColor = texture2D(uSampler0, vec2(vTextureCoord.s + delta, vTextureCoord.t + delta));
    vec4 boundsSWColor = texture2D(uSampler0, vec2(vTextureCoord.s - delta, vTextureCoord.t - delta));
    vec4 boundsSEColor = texture2D(uSampler0, vec2(vTextureCoord.s + delta, vTextureCoord.t - delta));
	
	vec2 boundsNW = boundsNWColor.rb + boundsNWColor.ga / 255.0;
    vec2 boundsNE = boundsNEColor.rb + boundsNEColor.ga / 255.0;
    vec2 boundsSW = boundsSWColor.rb + boundsSWColor.ga / 255.0;
    vec2 boundsSE = boundsSEColor.rb + boundsSEColor.ga / 255.0;
    
    vec2 bounds = vec2(max(max(boundsNW.x, boundsNE.x), max(boundsSW.x, boundsSE.x)),
                       max(max(boundsNW.y, boundsNE.y), max(boundsSW.y, boundsSE.y)));
    
    vec2 boundsLo = mod(bounds, 1.0 / 255.0);
    vec2 boundsHi = bounds - boundsLo;
    boundsLo *= 255.0;
	
	gl_FragColor = vec4(boundsHi.x, boundsLo.x, boundsHi.y, boundsLo.y);
}
</script>
<script id="shader-fs-scale" type="x-shader/x-fragment">
precision highp float;
precision highp int;

varying vec2 vTextureCoord;

uniform highp sampler2D uSampler0, uSampler1;
uniform int uTexSize;


void main(void)
{
	vec4 psiColor = texture2D(uSampler0, vTextureCoord);
	vec4 maxPsiModColor = texture2D(uSampler1, vec2(0.5, 0.5));
	
	vec2 psi = 2.0 * (psiColor.rb + psiColor.ga / 255.0) - 1.0;
	float maxPsiMod = maxPsiModColor.r + maxPsiModColor.g / 255.0;
    
    psi = (psi / maxPsiMod) / 2.0 + 0.5;
    
	vec2 psiLo = mod(psi, 1.0 / 255.0);
	vec2 psiHi = psi - psiLo;
	psiLo *= 255.0;
	
	gl_FragColor = vec4(psiHi.x, psiLo.x, psiHi.y, psiLo.y);
}
</script>
<script id="shader-fs-vis" type="x-shader/x-fragment">
precision highp float;
precision highp int;

varying vec2 vTextureCoord;

uniform highp sampler2D uSampler0, uSampler1;
uniform int uTexSize;


void main(void)
{
	vec4 psiColor = texture2D(uSampler0, vTextureCoord);
	vec4 vColor = texture2D(uSampler1, vTextureCoord);
	
	vec2 psi = 2.0 * (psiColor.rb - 0.5);
	float v = vColor.r;
    
    float psiNorm = min(sqrt(dot(psi, psi)), 1.0);
    float r = pow(max(dot(psi, vec2(1.0, 0.0)), 0.0), 0.3);
    float g = pow(max(dot(psi, vec2(-0.5, 0.866)), 0.0), 0.3);
    float b = pow(max(dot(psi, vec2(-0.5, -0.866)), 0.0), 0.3);
    float mult = 0.0;
    float maxRGB = max(max(r, g), b);
    if (maxRGB != 0.0) {
        mult = psiNorm / maxRGB;
    }
    r *= mult;
    g *= mult;
    b *= mult;
	
	gl_FragColor = vec4(r, g, b, 1.0);
}
</script>
<script type="text/javascript">
var N = 512;
var LOG_STEPS_PER_FRAME = 3;
var PROGRAMS = {
    'init': {
        'attr': ['aTextureCoord', 'aVertexPosition'],
        'fs': 'shader-fs-init',
        'un': [],
        'vs': 'shader-vs'
    },
    'initV': {
        'attr': ['aTextureCoord', 'aVertexPosition'],
        'fs': 'shader-fs-init-v',
        'un': [],
        'vs': 'shader-vs'
    },
    'getK': {
        'attr': ['aTextureCoord', 'aVertexPosition'],
        'fs': 'shader-fs-get-k',
        'un': ['uSampler0', 'uSampler1', 'uTexSize'],
        'vs': 'shader-vs'
    },
    'getPsiDot': {
        'attr': ['aTextureCoord', 'aVertexPosition'],
        'fs': 'shader-fs-get-psi-dot',
        'un': ['uSampler0', 'uSampler1', 'uSampler2', 'uSampler3', 'uTexSize'],
        'vs': 'shader-vs'
    },
    'eulerStep': {
        'attr': ['aTextureCoord', 'aVertexPosition'],
        'fs': 'shader-fs-euler-step',
        'un': ['uSampler0', 'uSampler1', 'uTexSize'],
        'vs': 'shader-vs'
    },
    'halfEulerStep': {
        'attr': ['aTextureCoord', 'aVertexPosition'],
        'fs': 'shader-fs-euler-step',
        'un': ['uSampler0', 'uSampler1', 'uTexSize'],
        'vs': 'shader-vs'
    },
    'getBounds': {
        'attr': ['aTextureCoord', 'aVertexPosition'],
        'fs': 'shader-fs-get-bounds',
        'un': ['uSampler0', 'uSampler1', 'uTexSize'],
        'vs': 'shader-vs'
    },
    'redBounds': {
        'attr': ['aTextureCoord', 'aVertexPosition'],
        'fs': 'shader-fs-red-bounds',
        'un': ['uSampler0', 'uTexSize'],
        'vs': 'shader-vs'
    },
    'scale': {
        'attr': ['aTextureCoord', 'aVertexPosition'],
        'fs': 'shader-fs-scale',
        'un': ['uSampler0', 'uSampler1', 'uTexSize'],
        'vs': 'shader-vs'
    },
    'vis': {
        'attr': ['aTextureCoord', 'aVertexPosition'],
        'fs': 'shader-fs-vis',
        'un': ['uSampler0', 'uSampler1', 'uTexSize'],
        'vs': 'shader-vs'
    }
};
var TEXTURES = ['psi', 'psiNew', 'k1', 'k2', 'k3', 'k4', 'k2Src', 'k3Src', 'k4Src', 'psiDot', 'v'];
var BUFFERS = {
    'quadVB': {
        'type': 'v',
        'data': [
            -1.0, -1.0,  1.0,
             1.0, -1.0,  1.0,
             1.0,  1.0,  1.0,
            -1.0,  1.0,  1.0
        ]
    },
    'quadTB': {
        'type': 't',
        'data': [
            0.0, 0.0,
            1.0, 0.0,
            1.0, 1.0,
            0.0, 1.0
        ]
    },
    'quadIB': {
        'type': 'i',
        'data': [0, 1, 2, 0, 2, 3]
    }
};

function getShader(gl, id) {
    var shaderScript = document.getElementById(id);
    var str = "";
    var k = shaderScript.firstChild;
    while (k) {
        if (k.nodeType == 3) {
            str += k.textContent;
        }
        k = k.nextSibling;
    }

    var shader;
    if (shaderScript.type == "x-shader/x-fragment") {
        shader = gl.createShader(gl.FRAGMENT_SHADER);
    } else if (shaderScript.type == "x-shader/x-vertex") {
        shader = gl.createShader(gl.VERTEX_SHADER);
    } else {
        return null;
    }

    gl.shaderSource(shader, str);
    gl.compileShader(shader);

    return shader;
}

function createProgram(progDesc) {
    var vertexShader = getShader(gl, progDesc.vs);
    var fragShader = getShader(gl, progDesc.fs);
    
    var prog = gl.createProgram();
    gl.attachShader(prog, vertexShader);
    gl.attachShader(prog, fragShader);
    gl.linkProgram(prog);
    
    prog.attr = {};
    for (var i = 0; i < progDesc.attr.length; i++) {
        var attrName = progDesc.attr[i];
        prog.attr[attrName] = gl.getAttribLocation(prog, attrName);
        gl.enableVertexAttribArray(prog.attr[attrName]);
    }
    
    prog.un = {};
    for (var i = 0; i < progDesc.un.length; i++) {
        var unName = progDesc.un[i];
        prog.un[unName] = gl.getUniformLocation(prog, unName);
    }
    
    return prog;
}

function createTexture(n) {
    var tex = gl.createTexture();
    tex.n = n;
    gl.bindTexture(gl.TEXTURE_2D, tex);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT );
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT );
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, n, n, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
    gl.bindTexture(gl.TEXTURE_2D, null);
    return tex;
}

function createBuffer(bd) {
    var glBufType = gl[{
        'v': 'ARRAY_BUFFER',
        't': 'ARRAY_BUFFER',
        'i': 'ELEMENT_ARRAY_BUFFER'
    }[bd.type]];
    var glArrType = {
        'v': Float32Array,
        't': Float32Array,
        'i': Uint16Array
    }[bd.type];
    var glItemSize = {
        'v': 3,
        't': 2,
        'i': 1
    }[bd.type];
    
    var buffer = gl.createBuffer();
    gl.bindBuffer(glBufType, buffer);
    gl.bufferData(glBufType, new glArrType(bd.data), gl.STATIC_DRAW);
    gl.bindBuffer(glBufType, null);
    
    buffer.itemSize = glItemSize;
    buffer.numItems = Math.floor(bd.data.length / glItemSize);
    
    return buffer;
}

function doRenderOp(progID, dstTexName, srcTexName, n) {
    var prog = progs[progID];

    gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
    
    gl.useProgram(prog);
    
    gl.uniform1i(prog.un.uTexSize, n);
    
    gl.bindBuffer(gl.ARRAY_BUFFER, buffers.quadVB);
    gl.vertexAttribPointer(prog.attr.aVertexPosition, buffers.quadVB.itemSize, gl.FLOAT, false, 0, 0);

    gl.bindBuffer(gl.ARRAY_BUFFER, buffers.quadTB);
    gl.vertexAttribPointer(prog.attr.aTextureCoord, buffers.quadTB.itemSize, gl.FLOAT, false, 0, 0);

    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffers.quadIB);
    
    if (!srcTexName.pop) {
        srcTexName = [srcTexName];
    }
    for (var i = 0; i < srcTexName.length; i++) {
        gl.activeTexture(gl.TEXTURE0 + i);
        gl.bindTexture(gl.TEXTURE_2D, texs[srcTexName[i]]);
        gl.uniform1i(prog.un['uSampler' + i], i);
    }
    
    if (dstTexName) {
        gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
        gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texs[dstTexName], 0);
    } else {
        gl.bindFramebuffer(gl.FRAMEBUFFER, null);
    }
    
    gl.drawElements(gl.TRIANGLES, buffers.quadIB.numItems, gl.UNSIGNED_SHORT, 0);
}

var gl;
function initGL(canvas) {
    gl = canvas.getContext("experimental-webgl");
    gl.viewportWidth = canvas.width;
    gl.viewportHeight = canvas.height;
}

var progs = {};
function initShaders() {
    for (var progID in PROGRAMS) {
        progs[progID] = createProgram(PROGRAMS[progID]);
    }
}

var framebuffer;
var texs = {};
function initTextureFramebuffer() {
    framebuffer = gl.createFramebuffer();
    for (var i = 0; i < TEXTURES.length; i++) {
        texs[TEXTURES[i]] = createTexture(N);
    }
    for (var n = N; n >= 1; n /= 2) {
        texs['red-' + n] = createTexture(n);
    }
}

var buffers = {};
function initBuffers() {
    for (var bn in BUFFERS) {
        buffers[bn] = createBuffer(BUFFERS[bn]);
    }
}

function getK(kTexName, srcTexName, vTexName) {
    doRenderOp('getK', kTexName, [srcTexName, vTexName], N);
}

function getPsiDot(psiDotTexName, k1Texname, k2TexName, k3TexName, k4TexName) {
    doRenderOp('getPsiDot', psiDotTexName, [k1Texname, k2TexName, k3TexName, k4TexName], N);
}

function eulerStep(psiNewTexName, psiTexName, psiDotTexName, halfStep) {
    if (halfStep) {
        doRenderOp('halfEulerStep', psiNewTexName, [psiTexName, psiDotTexName], N);
    } else {
        doRenderOp('eulerStep', psiNewTexName, [psiTexName, psiDotTexName], N);
    }
}

var frameNum = 0;
var frameNumStarted = new Date();
function updateFPS() {
    frameNum++;
    var now = new Date();
    if (now - frameNumStarted > 1000) {
        document.getElementById('fps').innerText = (1000 / ((now - frameNumStarted) / frameNum)).toFixed(2);
        frameNum = 0;
        frameNumStarted = now;
    }
}

function getBounds() {
    doRenderOp('getBounds', 'red-' + N, ['psi', 'v'], N);
    for (var n = N / 2; n >= 1; n /= 2) {
        doRenderOp('redBounds', 'red-' + n, ['red-' + 2 * n], n);
    }
}

function updateState() {
    // RK4
    getK('k1', 'psi', 'v');
    eulerStep('k2Src', 'psi', 'k1', true);
    getK('k2', 'k2Src', 'v');
    eulerStep('k3Src', 'psi', 'k2', true);
    getK('k3', 'k3Src', 'v');
    eulerStep('k4Src', 'psi', 'k3', false);
    getK('k4', 'k4Src', 'v');
    getPsiDot('psiDot', 'k1', 'k2', 'k3', 'k4');
    eulerStep('psiNew', 'psi', 'psiDot', false);
}

function scaleState() {
    doRenderOp('scale', 'psi', ['psiNew', 'red-1'], N);
}

function drawScene() {
    doRenderOp('vis', null, ['psi', 'v'], N);
}

function step() {
    updateFPS();
    for (var i = 0; i < LOG_STEPS_PER_FRAME; i++) {
        getBounds();
        updateState();
        scaleState();
    }
    drawScene();
}

function webGLStart() {
    var canvas = document.getElementById("display-canvas");
    initGL(canvas);
    initShaders();
    initBuffers();
    initTextureFramebuffer();
    doRenderOp('init', 'psi', [], N);
	doRenderOp('initV', 'v', [], N);

    setInterval(step, 0);
}
</script>
</head>
<body onload="webGLStart();">
  <canvas id="display-canvas" style="border: none;" width="512" height="512"></canvas>
  <p><strong>FPS: </strong><span id="fps">N/A</span></p>
</body>
</html>
